#! /usr/bin/python
from router import router, LINK_N, LINK_S, LINK_E, LINK_W
from routingtable import routingtable
from memorytable import memorytable
from memctrl import memctrl
from dmactrl import dmactrl
from cache import cache
from qos_gate import qos_gate
from constants import *
import random
import os

# simulation parameters
num_x = 8
num_y = 8

# create routing table
my_rtable = routingtable()

# create routers
my_routers = [None] * (num_x*num_y)
for x in range(num_x):
  for y in range(num_y):
    new_router = router(my_rtable)
    new_router.loc = (x, y)
    my_routers[x*num_y + y] = new_router

# linkup routers
for x in range(num_x):
  for y in range(num_y):
    cur_r = my_routers[x*num_y + y]
   
    if(y > 0):
      s_r = my_routers[x*num_y + (y-1)]
      cur_r.add_out(s_r, RCONSTS.OUT_BW, LINK_S)
   
    if(y < (num_y-1)):
      n_r = my_routers[x*num_y + (y+1)]
      cur_r.add_out(n_r, RCONSTS.OUT_BW, LINK_N)
    
    if(x > 0):
      w_r = my_routers[(x-1)*num_y + y]
      cur_r.add_out(w_r, RCONSTS.OUT_BW, LINK_W)
    
    if(x < (num_x-1)):
      e_r = my_routers[(x+1)*num_y + y]
      cur_r.add_out(e_r, RCONSTS.OUT_BW, LINK_E)

# create memaddr lookup table
my_mtable = memorytable(MCONSTS.NUM_LINES, SYSTEM.FLIT_P_LINE)

# create memories
my_memories = [None] * (num_x*num_y)
for x in range(num_x):
  for y in range(num_y):
    new_mc = memctrl(my_mtable)

    mc_loc = (x, y)
    my_rtable.add(new_mc.node_id, mc_loc)
    m_r = my_routers[mc_loc[0]*num_y + mc_loc[1]]
    m_r.add_out(new_mc, BINAGENT.IN_BW)
    new_mc.link_out(m_r, BINAGENT.OUT_BW)

    my_memories[x*num_y + y] = new_mc

dma_locs = [(3,3)]

my_dmas = []
for d_loc in dma_locs:
  new_d = dmactrl(my_mtable)

  my_rtable.add(new_d.node_id, d_loc)
  d_r = my_routers[d_loc[0]*num_y + d_loc[1]]

  d_r.add_out(new_d, BINAGENT.IN_BW)
  new_d.link_out(d_r, BINAGENT.OUT_BW)

  my_dmas.append(new_d)

special_nodes = []

my_caches = [None] * (num_x*num_y)
my_gates = [None] * (num_x*num_y)
for x in range(num_x):
  for y in range(num_y):
    new_c = cache(my_mtable)
    if (x, y) in special_nodes:
      new_g = qos_gate(1000,1000)
    else:
      new_g = qos_gate()

    c_loc = (x,y)
    my_rtable.add(new_c.node_id, c_loc)
    c_r = my_routers[c_loc[0]*num_y + c_loc[1]]

    c_r.add_out(new_c, BINAGENT.IN_BW)
    new_c.link_out(new_g, MAGIC.MAX_PSIZE)
    new_g.link_out(c_r, BINAGENT.OUT_BW)

    my_caches[x*num_y + y] = new_c
    my_gates[x*num_y + y] = new_g

    a_dma = my_dmas[0]
    new_c.dma_node = a_dma.node_id

agents = my_caches + my_gates + my_routers + my_memories + my_dmas

c1 = my_caches[2*num_y + 2]
c2 = my_caches[3*num_y + 3]
c3 = my_caches[4*num_y + 4]

for i in range(SYSTEM.STOPTIME):
  SYSTEM.TIME = i
  random.shuffle(agents)

  for agent in agents:
    agent.process()
  for agent in agents:
    agent.commit()

  r_util_bw = [router.stat_all_util_bw for router in my_routers]
  net_util_bw = sum(r_util_bw) / len(r_util_bw)
  for gate in my_gates:
    gate.set_net_util_bw(net_util_bw)
    gate.replenish()

  print "Executed Cycle " + str(i)

  if(i == SYSTEM.SLOWTIME):
    print "Turning off caches"
    for cache in my_caches: cache.active = False

#  for y in reversed(range(num_y)):
#    for x in range(num_x):
#      print my_routers[x*num_y + y].to_string().ljust(4),
#    print "   ",
#    for x in range(num_x):
#      print my_caches[x*num_y + y].to_string().ljust(4),
#    print ""
#  print ""

#  raw_input("Press enter for next cycle")
tot_norm = 0
tot_dma = 0
print "Dumping Statistics:"
print "# Requests (Normal, DMA)"
for y in reversed(range(num_y)):
  for x in range(num_x):
    my_cache = my_caches[x*num_y + y]
    tot_norm += my_cache.stat_resolved_requests
    print str(my_cache.stat_resolved_requests).ljust(4),
  print "   ",
  for x in range(num_x):
    my_cache = my_caches[x*num_y + y]
    tot_dma += my_cache.stat_resolved_dmas
    print str(my_cache.stat_resolved_dmas).ljust(4),
  print ""
print ""
print "Sum (normal, DMA, tot)", tot_norm, tot_dma, tot_norm + tot_dma
print "Avg. Latency (Normal, DMA)"
for y in reversed(range(num_y)):
  for x in range(num_x):
    my_cache = my_caches[x*num_y + y]
    if(my_cache.stat_resolved_requests > 0):
      print str(my_cache.stat_accum_req_time/my_cache.stat_resolved_requests).ljust(4),
    else:
      print "-".ljust(4),
  print "   ",
  for x in range(num_x):
    my_cache = my_caches[x*num_y + y]
    if(my_cache.stat_resolved_dmas > 0):
      print str(my_cache.stat_accum_dma_time/my_cache.stat_resolved_dmas).ljust(4),
    else:
      print "-".ljust(4),
  print ""
print ""
print "Max. Latency (Normal, DMA)"
for y in reversed(range(num_y)):
  for x in range(num_x):
    my_cache = my_caches[x*num_y + y]
    print str(my_cache.stat_max_req_time).ljust(4),
  print "   ",
  for x in range(num_x):
    my_cache = my_caches[x*num_y + y]
    print str(my_cache.stat_max_dma_time).ljust(4),
  print ""
print ""
print "Max. Router Utilization"
for y in reversed(range(num_y)):
    for x in range(num_x):
        my_router = my_routers[x*num_y + y]
        print "%0.2f" % my_router.stat_max_util_bw,
    print ""
print ""

def verify_cache_reg(line, cid):
  mid = my_mtable.lookup(line)
  for mem in my_memories:
    if(mem.node_id == mid):
      check = mem
      break
  dentry = check.directory.get(line,[])
  return cid in dentry
def verify_mem_reg(line, cid):
  for cache in my_caches:
    if(cache.node_id == cid):
      check = cache
      break
  lstate = check.data.get(line, LSTATE.I)
  return lstate not in [LSTATE.I, LSTATE.IP]

print "Verifying caches"
centry = 0
cerrors = 0
cireq = 0
for cache in my_caches:
  for line, lstate in cache.data.items():
    if lstate not in [LSTATE.I, LSTATE.IP]:
      if not verify_cache_reg(line, cache.node_id):
        cerrors += 1
      else: centry += 1
  cireq += cache.count_requests()
print "Verifying memory directories"
mentry = 0
merrors = 0
mlines = 0
mireq = 0
for mem in my_memories:
  for line, cids in mem.directory.items():
    if cids: mlines += 1
    for cid in cids:
      if not verify_mem_reg(line, cid):
        merrors += 1
      else: mentry += 1
  mireq += cache.count_requests()
print "Verification complete"

print "# cache entries verified: " + str(centry)
print "# cache errors: " + str(cerrors)
print "# directory entries verified (for # lines): " + str(mentry) + " (" + str(mlines) + ")"
print "# directory errors: " + str(merrors)

print "Incomplete Transactions:"
print "# in caches: " + str(cireq)
print "# in directory: " + str(mireq)
